package org.turbobytes.unikey.util.db.metadata;

import org.turbobytes.unikey.util.CollectionUtil;
import org.turbobytes.unikey.util.StringUtil;
import org.turbobytes.unikey.util.db.model.Column;
import org.turbobytes.unikey.util.db.model.DataType;
import org.turbobytes.unikey.util.db.DbException;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.util.*;

/**
 * Database operations that are generic to all databases
 *
 * @author Ganeshji Marwaha
 * @since Aug 7, 2010
 */
public abstract class GenericMetadata implements Metadata {

  protected static final String[] DB_TABLE_TYPES = { "TABLE" };

  protected static final String COL_TABLE_NAME  = "TABLE_NAME";
  protected static final String COL_COLUMN_NAME = "COLUMN_NAME";
  protected static final String COL_DATA_TYPE   = "DATA_TYPE";
  protected static final String COL_VIEW_NAME   = "VIEW_NAME";
  protected static final String COL_TYPE_NAME   = "TYPE_NAME";

  /**
   * NOTE: Works for all databases other than oracle or atleast that is an assumption.
   * So, OracleDialect will have a separate implementation
   * @param connection - to talk to the database
   * @return a list of table names in the given database
   */
  public List<String> getTableNames(Connection connection) {
    List<String> tables = new ArrayList<String>();

    try {
      DatabaseMetaData metaData = connection.getMetaData();
      ResultSet rs = metaData.getTables(null, null, null, DB_TABLE_TYPES);

      while (rs.next()) {
        String tableName = StringUtil.trim(rs.getString(COL_TABLE_NAME));
        if(StringUtil.isNotBlank(tableName)) {
          tables.add(tableName);
        }
      }
      return tables;

    } catch (SQLException e) {
      throw new DbException(e);
    }
  }
  
  public boolean isTablePresent(Connection connection, String tableName) {
    return CollectionUtil.containsIgnoreCase(getTableNames(connection), tableName);
  }

  public Column getColumn(Connection connection, String tableName, String columnName) {
    return getColumnNames(connection, tableName).get(columnName.toUpperCase());
  }

  public Map<String, Column> getColumnNames(Connection connection, String tableName) {
    Map<String, Column> columnMap = new HashMap<String, Column>();

    try {
      DatabaseMetaData metaData = connection.getMetaData();
      // NOTE: Oracle requires uppercase characters and mysql is case-insensitive when specifying table names
      ResultSet rs = metaData.getColumns(null, null, tableName.toUpperCase(), null);

      while (rs.next()) {
        String columnName = rs.getString(COL_COLUMN_NAME);
        String columnType = rs.getString(COL_TYPE_NAME);

        Column column = new Column();
        column.setName(columnName);
        column.setType(columnType);

        columnMap.put(columnName.toUpperCase(), column);
      }
      return columnMap;
    } catch (SQLException e) {
      throw new DbException(e);
    }
  }

  public boolean isColumnPresent(Connection connection, String tableName, String columnName) {
    return getColumnNames(connection, tableName).containsKey(columnName);
  }

  public List<String> getSupportedDbDataTypes(DataType dataType) {
    switch (dataType) {
      case WHOLE_NUMBER:
        return Arrays.asList("NUMBER");
      case TEXT:
        return Arrays.asList("CHAR", "VARCHAR", "VARCHAR2");
      default:
        throw new IllegalStateException(dataType + " data type is not expected. Developer Error.");
    }
  }


}
